/*
 * SRMI (an easy-to-use zero-code remoting library for Java)
 * Copyright (C) 2007  Mohammad Nabil Hussein
 * mohammad (dot) nabil (dot) h (at) gmail (dot) com
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

package SRMI.RemotingServices.ClassGeneration.Client;

import SRMI.RemotingServices.ClassGeneration.ClassGenerator;
import SRMI.RemotingServices.ClassGeneration.PassByRemoteStubGenerator;
import SRMI.RemotingServices.ClassGeneration.RemoteInterfaceGenerator;
import SRMI.RemotingServices.PassableByRemote;
import SRMI.SRMIException;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
 * We are supposed to Prepare a class that adapts the rmi stub that
 * implements 'remoteInterface' to a polymorphic instance of the
 * 'wrappedClass' class. i.e. 'wrappedClass' class should be assignable from
 * this adapter
 * @author meemo
 */

public class RmiStubAdapterGenerator extends ClassGenerator
{
    protected Class<?> remoteInterface;
    protected Class<?> remoteExceptionClass;
    
    /**
     * @param wrapped The class to generate a stub adapter to.
     */
    public RmiStubAdapterGenerator(Class<?> wrapped) throws SRMIException
    {
        Class<?> remoteInterface = new RemoteInterfaceGenerator(wrapped).GetGenerated();
        
        this.setWrappedClass(wrapped);
        this.remoteInterface = remoteInterface;
        assert remoteInterface != null : "null interface class";
        try
        {
            remoteExceptionClass = Class.forName("java.rmi.RemoteException");
        }
        catch (ClassNotFoundException ex)
        {
            ex.printStackTrace();
        }
    }
    
    protected void PrepareEmit()
    {
        PrepareDefaultWrapperEmit();
    }
    
    protected void EmitSupers()
    {
        file.print(String.format("extends %s",this.wrappedUnQualifiedClassName));
        
        // why the rmi stub adapter should be serializable ??
        // well, cuz remote classes who uses it can't return 
        // object, and if they don't return by-remote (i.e. within same JVM)
        // and the return type must be serializable (not Object).
        boolean serializlabe = false;
        for(Class interfase : getWrappedClass().getInterfaces())
        {
            if(interfase.equals(Serializable.class))
            {             
                serializlabe = true;
                break;
            }
        }
        if(!serializlabe)
        {
            file.print(" implements java.io.Serializable");
        }
    }
    
    protected void EmitConstructor()
    {
        file.println(String.format("\tpublic %s(Object rmistub, java.util.UUID uuid, String host)\n", generatedClassName));
        file.println(String.format("\t{\n\t\tthis.rmistub =  (%s) rmistub;\n\t\tthis.uuid = uuid;\n\t\tthis.host=host;\n\t}\n\n", remoteInterface.getName()));
    }
    
    protected void EmitMembersVariables()
    {
        file.println(String.format("\tprotected %s rmistub;\n\tpublic java.util.UUID uuid;\n\tpublic String host;\n\n",remoteInterface.getName()));
    }
    
    protected void EmitFunction(Method m) throws SRMIException
    {
        int mod = m.getModifiers();
        if(!Modifier.isPrivate(mod)
        && !Modifier.isStatic(mod)
        && !Modifier.isFinal(mod)
        )
        {
            boolean throwsRemoteException = ThrowsRemoteException(m);
            EmitModifiers(mod);
            file.print(" ");
            EmitType(m.getReturnType());
            file.print(" ");
            file.print(m.getName());
            file.print("(");
            EmitArgs(m, false);
            file.print(")");
            EmitExceptions(m.getExceptionTypes());
            file.print("\n\t{\n");
            if(throwsRemoteException)
            {
                EmitTryHeader();
                file.print("\t");
            }
            
            EmitWrappedCall(m);
            
            file.print("\n");
            if (throwsRemoteException)
            {
                EmitCatchHeader(remoteExceptionClass);
                file.print(String.format(
                        "\t\t\tex.printStackTrace();\n" +
                        "\t\t\treturn %s;\n" +
                        "\t\t}\n", GetErrorReturnValue(m)));
            }
            file.print("\t}\n\n");
        }
    }
    
    protected void PreEmitArgs(Method m) throws SRMIException
    {
        // get's the appropriate pass-by-remote objects for the args
        int number = 0;
        for(Class<?> param : m.getParameterTypes())
        {
            if(param.getAnnotation(PassableByRemote.class) != null)
            {
                PreparePassByRemoteParam(param,number);
            }
            number++;
        }
    }
    
    private void PreparePassByRemoteParam(Class<?> param, int number) throws SRMIException
    {
        //TODO get '_pass_by_remote' string from the generator itself
         
        // assure it is generated
        new PassByRemoteStubGenerator(param).Prepare();
        
        file.print(String.format(
                    "\t\t%s %s_PassByRemoteObj = %s != null ? new %s_pass_by_remote_stub(%s) : null;\n\t"                ,
                param.getName(), "p" + number, "p" + number, param.getName(), "p" + number));
    }
    
    protected void EmitArg(Class<?> param, boolean noEmitType, int number)
    {
        if(!noEmitType)
        {
            EmitType(param);
            file.print(" ");
        }
        if(noEmitType && param.getAnnotation(PassableByRemote.class) != null)
            file.print("p" + number + "_PassByRemoteObj");
        else
            file.print("p" + number);
    }
    
    protected void EmitWrappedCall(Method m) throws SRMIException
    {        
        PreEmitArgs(m);
        
        file.print(
                String.format(
                "\t\t%srmistub.%s(",
                ( m.getReturnType().equals(java.lang.Void.TYPE)?"":"return "),
                m.getName( ) ) ) ;
        EmitArgs(m,true);
        file.print(");");
    }
    
    //TODO: see more appropriate value
    protected String GetErrorReturnValue(Method m)
    {
        if(m.getReturnType().equals(java.lang.Void.TYPE))
            return "";
        if(!m.getReturnType().isPrimitive())
            return "null";//"new " + m.getReturnType().getCanonicalName() + "()";
        else
            if(m.getReturnType().getName().equals( "int" )
            || m.getReturnType().getName().equals( "float" )
            || m.getReturnType().getName().equals( "double" )
            || m.getReturnType().getName().equals( "byte" )
            || m.getReturnType().getName().equals( "short" )
            || m.getReturnType().getName().equals( "long" ) )
                return "-1";
            else if(m.getReturnType().getName().equals( "boolean" ))
                return "false";
            else //char
                return "'?'";
    }
    
    private boolean ThrowsRemoteException(Method m)
    {
        Method remoteCounterpart = null;
        try
        {
            remoteCounterpart = this.remoteInterface.getMethod(
                    m.getName(), m.getParameterTypes());
        }
        catch (Exception ex)
        {
            return false; // if method is not found in the remote
            // interface then it doesn't throw remote exception
        }
        return remoteCounterpart != null;
    }
    
    protected String getGeneratedClassNameSuffix()
    {
        return "_clientside_rmistub_adapter";
    }
}
